﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.Hosting;
using System.Web.UI;
using System.Xml;
using Jayden.Dll.Core.Extensions;
using Jayden.Dll.Core.Translations;
using Jayden.Dll.Web.Enums;
using Jayden.Dll.Web.HtmlControls.Descriptions;

namespace Jayden.Dll.Web.HtmlControls.Generics
{
	public abstract class HtmlControl
	{
		private HtmlControlDescription m_Description;
		private HtmlControl m_Parent;
		private bool m_ServerRender = true;

		public HtmlControl(HtmlControlDescription description) { m_Description = description; }


		public HtmlControlDescription Description { get { return m_Description; } }
		public bool ServerRender { get { return m_ServerRender && (Parent == null || Parent.ServerRender); } set { m_ServerRender = value; } }
		public HtmlControl Root { get { if (m_Parent == null) return this; return Parent.Root; } }
		public HtmlControl Parent { get { return m_Parent; } set { m_Parent = value; } }
		public abstract void Load(XmlNode node, HtmlControlLoader loader);
		public virtual void ProcessBinRequest(HttpHandlerContext context) { }
		public abstract void ProcessHtmlRequest(HttpHandlerContext context);
		public abstract void ProcessJsonRequest(HttpHandlerContext context);
		public virtual void ProcessJsRequest(HttpHandlerContext context) { }
		public virtual void ProcessCssRequest(HttpHandlerContext context) { }
		public virtual void TranslateControl(ITranslator translator) { }
		public abstract void Render(HtmlTextWriter writer, RenderMode mode, int level, bool innerHtml = false);

		public string ToInnerHtml(ITranslator translator) { return ToString(RenderMode.Production, true, true, translator); }
		public string ToOuterHtml(ITranslator translator) { return ToString(RenderMode.Production, true, false, translator); }

		public override string ToString() { return ToString(RenderMode.Production, false); }
		public string ToString(RenderMode mode, bool clear, bool innerHtml = false, ITranslator translator = null)
		{
			if (translator != null)
				TranslateControl(translator);
			if (clear)
			{
				CleanAttributes(new Regex("data\\-server\\-.*"), true);
				CleanAttributes(new Regex("data\\-clean\\-.*"), true);
			}
			using (StringWriter writer = new StringWriter())
			{
				HtmlTextWriter htmlWriter = new HtmlTextWriter(writer);
				Render(htmlWriter, mode, 0, innerHtml);
				htmlWriter.Flush();
				return writer.ToString();
			}
		}
		public virtual int CleanAttributes(Regex regex, bool recursive) { return 0; }

		private static Dictionary<string, Type> m_Types;
		private static Dictionary<string, HtmlElementDescription> m_Descriptions;

		public static Type GetType(string tagName, HtmlControlLoader loader)
		{
			Type result = null;
			if (loader != null)
				result = loader[tagName];
			if (result == null)
				m_Types.TryGetValue(tagName, out result);
			return result;
		}

		public static IEnumerable<HtmlElementDescription> ElementDescriptions { get { return new List<HtmlElementDescription>(m_Descriptions.Values); } }

		private static string m_ElementTagPattern = null;
		public static string ElementTagPattern { get { return m_ElementTagPattern; } }

		static HtmlControl()
		{
			Type thisType = typeof(HtmlControl);
			Type descriptionType = typeof(HtmlControlDescription);
			m_Types = new Dictionary<string, Type>();
			m_Descriptions = new Dictionary<string, HtmlElementDescription>();
			List<string> tags = new List<string>();
			foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
			{
				if (thisType.IsAssignableFrom(t) && t.GetConstructor(Type.EmptyTypes) != null && !t.IsAbstract)
					m_Types.Add(t.Name.ToLower(), t);
				else if (descriptionType.IsAssignableFrom(t) && !t.IsAbstract)
				{
					FieldInfo instanceField = t.GetField("Instance");
					if (instanceField != null)
					{
						HtmlElementDescription description = instanceField.GetValue(null) as HtmlElementDescription;
						// cast in htmlelement can result in null value (and wrong init too :p)
						if (description != null)
						{
							m_Descriptions.Add(description.TagName, description);
							tags.Add(description.TagName);
						}
					}
				}
			}
			m_ElementTagPattern = string.Format("({0})", string.Join("|", tags)) + "{1}";
		}

		public static HtmlControl[] LoadHtmlControls(string html, HtmlControlLoader loader, HtmlElementDescription into)
		{
			try
			{
				html = string.Format("<{0}>{1}</{0}>", into.TagName, html);
				XmlDocument doc = new XmlDocument();
				List<HtmlControl> controls = new List<HtmlControl>();
				doc.LoadXml(html);
				foreach (XmlNode node in doc.DocumentElement.ChildNodes)
					controls.Add(HtmlControl.LoadControl(node, loader));
				return controls.ToArray();
			}
			catch (Exception e)
			{
				throw new Jayden.Dll.Core.Exceptions.Exception("Unable to load html controls with '{0}'.", e, html);
			}
		}
		public static HtmlControl LoadHtmlControl(string html, HtmlControlLoader loader)
		{
			try
			{
				XmlDocument doc = new XmlDocument();
				doc.LoadXml(html);
				return HtmlControl.LoadControl(doc.DocumentElement, loader);
			}
			catch (Exception e)
			{
				throw new Jayden.Dll.Core.Exceptions.Exception("Unable to load html control with '{0}'.", e, html);
			}
		}
		public static HtmlControl LoadControl(string filename, HtmlControlLoader loader, XmlNode overridingNode = null)
		{
			try
			{
				XmlDocument doc = new XmlDocument();
				doc.Load(filename);
				if (overridingNode != null && overridingNode is XmlElement)
					doc.DocumentElement.OverrideAttributes(overridingNode as XmlElement);
				//Jayden.Dll.Web.Helper.DesignerHelper designer = new Helper.DesignerHelper();
				//designer.BuildDesignerFromXmlDocument(doc, loader);
				return HtmlControl.LoadControl(doc.DocumentElement, loader);
			}
			catch (Exception e)
			{
				throw new Jayden.Dll.Core.Exceptions.Exception("Unable to load html control from path '{0}'.", e, filename);
			}
		}
		public static HtmlControl LoadControl(XmlNode elt, HtmlControlLoader loader)
		{
			if (elt is XmlText)
			{
				XmlText xmlText = elt as XmlText;
				HtmlText text = new HtmlText();
				text.Load(elt, loader);
				return text;
			}
			else if (elt is XmlElement)
			{
				string tagName = elt.Name.ToLowerInvariant();
				Type type = null;
				XmlAttribute serverLoad = elt.Attributes["data-server-load"];
				if (serverLoad != null)
				{
					string virtualPath = serverLoad.Value;
					string physicalPath = HostingEnvironment.MapPath(virtualPath);
					elt.Attributes.Remove(serverLoad);
					HtmlElement element = LoadControl(physicalPath, loader, elt) as HtmlElement;
					return element;
				}
				XmlAttribute serverType = elt.Attributes["data-server-type"];
				if (serverType != null)
				{
					type = Type.GetType(serverType.Value);
#if DEBUG
					if(type == null)
						throw new Exception(string.Format("Unknow type for '{0}'.", elt.OuterXml));
#endif
				}
				else if (loader != null)
					type = loader[elt.Name.ToLowerInvariant()];
				if(type == null)
					m_Types.TryGetValue(elt.Name.ToLowerInvariant(), out type);
				if(type != null)
				{
					HtmlElement element = Activator.CreateInstance(type) as HtmlElement;
					if (element != null)
						element.Load(elt, loader);
					return element;
				}
#if DEBUG
				throw new Exception(string.Format("Unknow type for '{0}'.", elt.OuterXml));
#endif
			}
			return null;
		}
	}
}
